package sketch;
import java.util.List;

import sketch.ast.ASTUtils;
import sketch.ast.MethodDeclarationWithHoles;
import sketch.generator.GenTests;
import sketch.generator.JUnitTestCreator;
import sketch.generator.SequenceGenerator;
import sketch.skeleton.GeneratorMaps;
import sketch.util.Checker;
import sketch.util.Globals;
import junit.framework.TestCase;

public abstract class GenTest extends TestCase {
	
	@Override
	public void setUp() {
		// this line should be able to set dynamically
		GenTests.reset(this.testedClasses());
	}

	/**
	 * The call back testing method by JUnit
	 * */
	public void testRunning() {
		@SuppressWarnings("rawtypes")
		GenTests genTests = new GenTests(this.scratchTestPath());
		
		MethodDeclarationWithHoles methodWithHoles = ASTUtils.createMethodWithHoles(this.scratchTestPath());
		
		Checker.checkTrue(genTests.numOfGenerators() == 0, "The number of generator should be zero");
		//collect test generators
		try {
			GeneratorMaps.resetCounter();
			//XXX here may be problematic!
			//call the client method to collect generators
			scratchTest();
			this.checkValidity(genTests);
		} catch (Exception exception) {
			System.out.println("ignore any exception in the first round: " + exception.getMessage());
		}
		
		Class<?>[] generatorTypes = genTests.getGeneratorTypes();
		StringBuilder sb = new StringBuilder();
		
		//generate tests
		int generated_num = 0;
		String testCaseName = this.testClassName();
		while (genTests.hasNextTest()) {
			//generate a test
			SketchSequence generated = genTests.nextTest();
			
			System.out.println("---- start a new round for generating test: " + (generated_num++) + " ----");
			GeneratorMaps.resetCounter();
			//call the client method
			scratchTest();

			//check the validity of current test generator
			this.checkValidity(genTests);
			
			//get the currently generated test, and check that is correct
			SketchSequence currentTest = genTests.currentTest();
			Checker.checkTrue(generated.equals(currentTest), "The current test field is not correctly set!");
			
			List<BatchExecution> executionResults = genTests.currentExecutionResults();
			for(BatchExecution exe : executionResults) {
				System.out.println(exe);
			}
			
			List<ExecutableSequenceContent[]> contentList = genTests.currentExecutableContents();
			
			//TEST_NUM++;
			
			//fill in holes with created sequence and values
			generated.setMethodDeclarationWithHoles(methodWithHoles);
			generated.setExecutableSequenceContent(contentList);
			generated.renameTest(testCaseName + generated_num);
			//fill in the generator holes
			generated.fillinHoles(generatorTypes);
			
			//construct the text
			String generatedTest = generated.outputGeneratedTestMethod();
			sb.append(generatedTest);
			sb.append(Globals.lineSep);
			
			System.out.println(generatedTest);			
			System.out.println(Globals.lineSep);
			
			//System.out.println(JUnitTestCreator.createSingleTest(generated, TEST_CASE_NAME + (TEST_NUM)));
		}
		
		String[] necessaryImports = ASTUtils.extractImportDeclarationsAsString(this.scratchTestPath());
		
		JUnitTestCreator.createJUnitTest(sb.toString(), this.testOutputPath(),  "TreeMapTest", necessaryImports);
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void checkValidity(GenTests genTests) {
		//the number of generators
		int numOfGeneratorsBeforeUpdate = genTests.numOfGenerators();
		//adding each generators
		for(SequenceGenerator<?> generator : GeneratorMaps.collectGenerators()) {
			if(!genTests.hasGenerator(generator)) {
				genTests.addGenerator(generator);
			}	
		}
		int numOfGeneratorsAfterUpdate = genTests.numOfGenerators();
		//do some checking here
		Checker.checkTrue(genTests.numOfGenerators() > 0, "The number of generators should > 0");
		if(numOfGeneratorsBeforeUpdate != 0) {
		  Checker.checkTrue(numOfGeneratorsBeforeUpdate == numOfGeneratorsAfterUpdate,
				  "The number of generators should not be changed");
		}
	}
	
	/**
	 * The Tested classes
	 * */
	public abstract Class<?>[] testedClasses();
	
	/**
	 * the path to output tests
	 * */
	public abstract String testOutputPath();

	/**
	 * The test class name
	 * */
	public abstract String testClassName();
	
	/**
	 * The place to provide inputs and initialize the tool
	 * */
	public abstract String scratchTestPath();

	/**
	 * The test which subclass needs to override
	 * */
	public abstract void scratchTest();
	
}
